﻿namespace Blaze.Editor.UI
{
    using UnityEngine;
    using UnityEditor;
    using System.Collections.Generic;
    using System;
    using System.Reflection;

    public delegate void SelectionHandler(object selected, int col);

    public class TableView : IDisposable
    {
        public event SelectionHandler ItemSelected;

        public TableViewAppr Appearance { get { return _appearance; } }

        public TableView(EditorWindow hostWindow, Type itemType)
        {
            m_hostWindow = hostWindow;
            m_itemType = itemType;
        }

        public void Dispose()
        {

        }

        public void ClearColumns()
        {
            m_descArray.Clear();
        }

        public bool AddColumn(string colDataPropertyName, string colTitleText, float widthByPercent, TextAnchor alignment = TextAnchor.MiddleCenter, string fmt = "")
        {
            TableViewColDesc desc = new TableViewColDesc();
            desc.PropertyName = colDataPropertyName;
            desc.TitleText = colTitleText;
            desc.Alignment = alignment;
            desc.WidthInPercent = widthByPercent;
            desc.Format = string.IsNullOrEmpty(fmt) ? null : fmt;
            desc.FieldInfo = m_itemType.GetField(desc.PropertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
            if (desc.FieldInfo == null)
            {
                Debug.LogWarningFormat("Field '{0}' accessing failed.", desc.PropertyName);
                return false;
            }

            m_descArray.Add(desc);
            return true;
        }

        public void RefreshData(List<object> entries, Dictionary<object, Color> specialTextColors = null)
        {
            m_lines.Clear();

            if (entries != null && entries.Count > 0)
            {
                m_lines.AddRange(entries);

                SortData();
            }

            m_specialTextColors = specialTextColors;
        }

        public void Draw(Rect area)
        {
            GUILayout.BeginArea(area);
            _scrollPos = GUILayout.BeginScrollView(_scrollPos, GUIStyle.none, GUI.skin.verticalScrollbar);
            //Debug.LogFormat("scroll pos: {0:0.00}, {1:0.00}", _scrollPos.x, _scrollPos.y);
            {
                GUIStyle s = new GUIStyle();
                s.fixedHeight = _appearance.LineHeight * m_lines.Count;
                s.stretchWidth = true;
                Rect r = EditorGUILayout.BeginVertical(s);
                {
                    // this silly line (empty label) is required by Unity to ensure the scroll bar appear as expected.
                    BlazeEditorUtil.DrawLabel("", _appearance.Style_Line);

                    DrawTitle(r.width);

                    // these first/last calculatings are for smart clipping 
                    int firstLine = Mathf.Max((int)(_scrollPos.y / _appearance.LineHeight) - 1, 0);
                    int shownLineCount = (int)(area.height / _appearance.LineHeight) + 2;
                    int lastLine = Mathf.Min(firstLine + shownLineCount, m_lines.Count);

                    for (int i = firstLine; i < lastLine; i++)
                    {
                        DrawLine(i + 1, m_lines[i], r.width);
                    }
                }
                EditorGUILayout.EndVertical();
            }
            GUILayout.EndScrollView();
            GUILayout.EndArea();
        }

        public void SetSortParams(int sortSlot, bool descending)
        {
            _sortSlot = sortSlot;
            _descending = descending;
        }

        public void SetSelected(object obj)
        {
            m_selected = obj;

            if (ItemSelected != null)
                ItemSelected(obj, 0);
        }

        public object GetSelected()
        {
            return m_selected;
        }

        private void DrawTitle(float width)
        {
            for (int i = 0; i < m_descArray.Count; i++)
            {
                var desc = m_descArray[i];

                Rect r = LabelRect(width, i, 0);
                bool selected = _sortSlot == i;
                GUI.Label(r, desc.TitleText + (selected ? _appearance.GetSortMark(_descending) : ""), _appearance.GetTitleStyle(selected));
                if (Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition))
                {
                    if (_sortSlot == i)
                    {
                        _descending = !_descending;
                    }
                    else
                    {
                        _sortSlot = i;
                    }

                    SortData();
                    m_hostWindow.Repaint();
                }
            }
        }

        private void DrawLine(int pos, object obj, float width)
        {
            Rect r = new Rect(0, pos * _appearance.LineHeight, width, _appearance.LineHeight);
            bool selectionHappens = Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition);

            GUIStyle style = new GUIStyle((pos % 2 != 0) ? _appearance.Style_Line : _appearance.Style_LineAlt);
            if (selectionHappens)
            {
                m_selected = obj;
            }

            // note that the 'selected-style' assignment below should be isolated from the if-conditional statement above
            // since the above if is a one-time event, on the contrary, the 'selected-style' assignment below should be done every time in the drawing process
            if (m_selected == obj)
            {
                style = _appearance.Style_Selected;
            }
            else
            {
                Color specialColor;
                if (m_specialTextColors != null &&
                    m_specialTextColors.TryGetValue(obj, out specialColor))
                {
                    style.normal.textColor = specialColor;
                }
            }

            for (int i = 0; i < m_descArray.Count; i++)
                DrawLineCol(pos, i, width, obj, style, selectionHappens);
        }

        private void DrawLineCol(int pos, int col, float width, object obj, GUIStyle style, bool selectionHappens = false)
        {
            var rect = LabelRect(width, col, pos);

            if (selectionHappens && rect.Contains(Event.current.mousePosition))
            {
                m_selectedCol = col;
                if (ItemSelected != null)
                    ItemSelected(obj, col);
                m_hostWindow.Repaint();
            }

            var desc = m_descArray[col];
            var text = desc.FormatObject(obj);

            // note that the 'selected-style' assignment below should be isolated from the if-conditional statement above
            // since the above if is a one-time event, on the contrary, the 'selected-style' assignment below should be done every time in the drawing process
            if (m_selectedCol == col && m_selected == obj)
            {
                style = _appearance.Style_SelectedCell;
            }

            style.alignment = desc.Alignment;
            GUI.Label(rect, new GUIContent(text, text), style);
        }

        private void SortData()
        {
            m_lines.Sort((s1, s2) =>
            {
                if (_sortSlot >= m_descArray.Count)
                    return 0;

                return m_descArray[_sortSlot].Compare(s1, s2) * (_descending ? -1 : 1);
            });
        }

        private Rect LabelRect(float width, int slot, int pos)
        {
            float accumPercent = 0.0f;
            int count = Mathf.Min(slot, m_descArray.Count);
            for (int i = 0; i < count; i++)
            {
                accumPercent += m_descArray[i].WidthInPercent;
            }
            return new Rect(width * accumPercent, pos * _appearance.LineHeight, width * m_descArray[slot].WidthInPercent, _appearance.LineHeight);
        }

        List<TableViewColDesc> m_descArray = new List<TableViewColDesc>();
        TableViewAppr _appearance = new TableViewAppr();

        Vector2 _scrollPos = Vector2.zero;

        int _sortSlot = 0;
        bool _descending = true;

        Type m_itemType = null;
        EditorWindow m_hostWindow = null;
        List<object> m_lines = new List<object>();

        object m_selected = null;
        int m_selectedCol = -1;

        Dictionary<object, Color> m_specialTextColors;
    }
}